export interface ISubscription {
  /**

   * @memberof ISubscription
   */
  renew(): void

  /**
   * @memberof ISubscription
   */
  cancel(): void
}

export type ChangeObserver<T = never> = (newValue: T) => void

import { createSubscription } from "../utils/Subscription"
import ObservableObject from "./observable.object"

export default class Observable<T = any> {
  _observerNotifying: boolean = false
  public readonly isObservable: boolean = true
  public _dirtyObservable: boolean = false
  public _activeObservable: boolean = true
  private _changeObservers = new Set<ChangeObserver<T>>()
  private _parentObservables: Set<Observable<any>> = new Set()
  private _childObservables: Set<Observable<any>> = new Set()
  constructor() {
    Object.defineProperties(this, {
      isObservable: {
        value: this.isObservable,
        writable: !1,
        enumerable: !1
      },
      _dirtyObservable: {
        value: this._dirtyObservable,
        enumerable: !1
      },
      _activeObservable: {
        value: this._activeObservable,
        enumerable: !1
      },
      _observerNotifying: {
        value: this._observerNotifying,
        enumerable: !1
      },
      _changeObservers: {
        value: this._changeObservers,
        writable: !1,
        enumerable: !1
      },
      _childObservables: {
        value: this._childObservables,
        writable: !1,
        enumerable: !1
      },
      _parentObservables: {
        value: this._parentObservables,
        writable: !1,
        enumerable: !1
      }
    })
  }
  onChanged(observer: ChangeObserver<T>) {
    if (this._changeObservers.has(observer))
      throw new Error("This observer function is already observing this Observable, and double subscriptions are not supported.")
    return createSubscription(
      () => this._changeObservers.add(observer),
      () => this.removeOnChanged(observer),
      !0
    )
  }
  removeOnChanged(observer: ChangeObserver<T>) {
    this._changeObservers.delete(observer)
  }
  protected notifyObservers() {
    if (this._dirtyObservable && !this._observerNotifying && this._activeObservable) {
      this._observerNotifying = !0
      for (const changeObserver of this._changeObservers) this.notifyObserver(changeObserver)
      this._dirtyObservable = !1
      this._observerNotifying = !1
    }
  }
  notifyObserver(observer: ChangeObserver<T>) {
    observer(undefined)
  }
  notifyDown() {
    for (const childObservables of this._childObservables) childObservables.notifyDown()
    this.notifyObservers()
  }
  notifyUp() {
    for (const parentObservables of this._parentObservables) parentObservables.notifyUp()
    this.notifyObservers()
  }
  setDirty(notifyUp = !0) {
    this.setDirtyUp()
    notifyUp && this.notifyUp()
  }
  setDirtyUp() {
    if (!this._dirtyObservable) {
      this._dirtyObservable = !0
      for (const parentObservables of this._parentObservables) parentObservables.setDirtyUp()
    }
  }
  addChildObservable(obj: T | Observable) {
    Observable.isObservable(obj) && (this._childObservables.add(obj), obj._parentObservables.add(this))
  }
  removeChildObservables(obj: T | Observable) {
    Observable.isObservable(obj) && (this._childObservables.delete(obj), obj._parentObservables.delete(this))
  }
  atomic(func: () => void) {
    if (!this._activeObservable) return func()
    try {
      this.setActive(!1), func()
    } finally {
      this.setActive(!0)
    }
  }
  setActive(value: boolean) {
    const oldValue = this._activeObservable
    this._activeObservable = value
    if (!oldValue && value) {
      this.notifyObservers()
    }
    for (const child of this._childObservables) {
      child.setActive(value)
    }
  }
  deepCopy() {
    throw Error("deepCopy is only implemented in subclasses of Observable")
  }
  static isObservable(e: any): e is ObservableObject {
    return e && e.isObservable
  }
}
